package neptune.manager.allocator;

import it.luigiarpino.kernighanLin.LinKernighanAmmissibile;
import it.luigiarpino.kernighanLin.LinKernighanException;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import neptune.manager.model.cluster.Cluster;
import neptune.manager.model.cluster.PhysicalNode;
import neptune.manager.model.experiment.Experiment;
import neptune.manager.model.topology.Link;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.Property;
import neptune.manager.model.topology.Topology;
import neptune.manager.storage.XMLClusterStorageFactory;
import neptune.manager.storage.XMLTopologyStorage;

/**
 * Fornisce la mappa per l'allocazione dell'esperimento
 * secondo l'algoritmo di Lin-Kernighan
 * 
 * @author Luigi Lorenzo Arpino [luigiarpino@yahoo.it]
 *
 */
public class LKAllocationMap
{
	
	public static void main(String[] args) throws Exception
	{
		String expId="pluto";
		int maxVMsPerNode = 3;
		int maxBandwidth=100;
		// /home/luigi/.emuPlugin/cluster.xml
		XMLClusterStorageFactory clusterStorage=new XMLClusterStorageFactory("/home/luigi/.emuPlugin/cluster.xml");
		Cluster cluster = clusterStorage.getClusterStorage().getCluster();
		
		File file=new File("/home/luigi/.emuPlugin/topologies/"+expId+".topology.xml");
		XMLTopologyStorage storage=new XMLTopologyStorage(file,file);
		Topology topology = storage.getTopology();
		Experiment experiment=new Experiment("admin",expId);
		experiment.setTopologyWithoutControls(topology);
		
		LKAllocationMap lkMap = new LKAllocationMap(experiment, cluster, maxVMsPerNode, maxBandwidth);
		//Map<String, String[]> allocationMap = lkMap.getMap();
		System.out.println( lkMap.getMessaggio() );
	}
	
	
	private Map<String, String[]> allocationMap;
	private String[] vms;
	private int[] partizionamento;
	private String messaggio;
	
	public LKAllocationMap(Experiment experiment, Cluster cluster, int maxVMsPerNode, int maxBandwidth) throws AllocationException
	{
		// Costruisco il vettore delle macchine virtuali dell'esperimento
		String expId=experiment.getExperimentId();
		Topology topology = experiment.getTopology();
		Node[] node=topology.getNodes();
		int numMacchineVirtuali=node.length;
		this.vms=new String[node.length];
		for(int i=0; i<node.length; i++)
		{
			vms[i]=node[i].getId();
		}
		// Costruisco la lista delle macchine fisiche che non
		// contengono macchine virtuali
		PhysicalNode[] phyNodes = cluster.getNodes();
		if (phyNodes == null )
		{
			String msg="\n Unable to allocate experiment '"+expId+"'."
			+"\n The cluster is empty."
			+"\n Please insert new physical machines!";
			throw new AllocationException(msg);
		}
		List<String> listaIDFreePhysicalNodes=new LinkedList<String>();
		List<String> listaNameFreePhysicalNodes=new LinkedList<String>();
		for(int i=0; i<phyNodes.length; i++)
		{
			if(phyNodes[i].getHostedNodes().length == 0)
			{
				listaIDFreePhysicalNodes.add(phyNodes[i].getId());
				listaNameFreePhysicalNodes.add(phyNodes[i].getName());
			}
		}
		int macchineFisicheNecessarie = (int)Math.ceil( (double)numMacchineVirtuali/(double)maxVMsPerNode);
		int macchineFisicheDisponibili = listaIDFreePhysicalNodes.size();
		// Verifico la disponibilita' delle macchine fisiche
		if (macchineFisicheNecessarie > macchineFisicheDisponibili)
		{
			String msg="\n Unable to allocate experiment "+expId
				+"\n There are only "+macchineFisicheDisponibili+" free physical machine"
				+"\n The experiment "+expId+" needs "+macchineFisicheNecessarie +" physical machine";
			throw new AllocationException(msg);
		}
		// Costruisco la matrice di adiacenza
		// Inizializzazione della matrice di adiacenza
		int[][] matrice=new int[numMacchineVirtuali][numMacchineVirtuali];
		for(int row=0; row<numMacchineVirtuali; row++)
		{
			for(int col=0; col<numMacchineVirtuali; col++)
			{
				matrice[row][col]=0;
			}
		}
		//  della matrice di adiacenza 
		Link[] links = topology.getLinks();
		for(int i=0; i<links.length; i++)
		{
			String source = links[i].getSource().getNodeId();
			int indexSource=getIndexOfVirtualMachine(source);
			String dest = links[i].getDestination().getNodeId();
			int indexDest=getIndexOfVirtualMachine(dest);
			Property[] property = links[i].getProperties();
			for(int k=0 ; k<property.length ; k++)
			{
				if (property[k].getName().equalsIgnoreCase(Property.BANDWIDTH))
				{
					int bandwith=getBandwidth(property[k].getValue());
					matrice[indexDest][indexSource]=bandwith;
					matrice[indexSource][indexDest]=bandwith;
				}
			}
		}
		 
		int taglioMax=maxBandwidth; 
		int maxNodi=maxVMsPerNode;
		
		try 
		{
			//LinKernighanAmmissibile lk=new LinKernighanAmmissibile(matrice, numeroPartizioni, taglioMax, maxNodi);
			LinKernighanAmmissibile lk=new LinKernighanAmmissibile(matrice, taglioMax, maxNodi);
			this.partizionamento=lk.getPartizionamento();
		}
		catch (LinKernighanException e) 
		{
			String msg="\n Unable to allocate experiment: '"+expId+"'"
				+"\n"+e.getMessage();
			throw new AllocationException(msg);
		}
			
		// Calcolo la mappa di allocazione delle macchine virtuali
		this.allocationMap = new HashMap<String, String[]>(0);
		this.messaggio=new String("\nTopology mapping:\n");
		//for(int i=0; i< phyNodes.length ; i++)
		for(int i=0; i< listaIDFreePhysicalNodes.size() ; i++)
		{
			//String idPhysicalMachine = phyNodes[i].getId();
			String idPhysicalMachine = listaIDFreePhysicalNodes.get(i);
			String[] virtualMachines=getVMs(i);
			allocationMap.put(idPhysicalMachine, virtualMachines);
			//this.messaggio += "\nOn physical machine "+phyNodes[i].getName()+":";
			this.messaggio += "\nOn physical machine "+listaNameFreePhysicalNodes.get(i)+":";
			for(int k=0;k<virtualMachines.length; k++)
			{
				this.messaggio +=" "+virtualMachines[k]; 
			}
		}
		this.messaggio +="\n";
	}
	
	/**
	 * Fornisce  il valore della banda in megabit
	 * @param bandwidth
	 * @return
	 */
	private int getBandwidth(String bandwidth)
	{
		if (bandwidth.contains("kbps"))
		{
			bandwidth=bandwidth.replace("kbps", "");
		}
		else
		{
			
		}
		Integer  bandaKbps = new Integer(bandwidth);
		int bandaMegabit = (int)Math.ceil( (double)bandaKbps.intValue()/(double)1000 );
		return bandaMegabit;
	}
	
	private int getIndexOfVirtualMachine(String vmName)
	{
		for(int i=0; i<this.vms.length; i++)
		{
			if (vms[i].equalsIgnoreCase(vmName))
			{
				return i;
			}
		}
		return -1;
	}
	
	
	public Map<String, String[]> getMap()
	{
		return this.allocationMap;
	}
	
	/**
	 * Restituisce la descrizione dell'allocazione effettuata
	 * @return
	 */
	public String getMessaggio()
	{
		return this.messaggio;
	}
	
	/**
	 * Restituisce il numero di macchine virtuali da allocare sulla macchina
	 * fisica di indice index
	 * 
	 * @param index l'indice della macchina fisica
	 */
	private int getNumOfVMs(int index)
	{
		int count=0;
		for(int i=0; i<partizionamento.length; i++)
		{
			if (partizionamento[i]==index)
			{
				count ++;
			}
		}
		return count;
	}
	
	/**
	 * Restituisce il vettore delle macchine virtuali
	 * da allocare sulla machina fisica di indice index
	 *  
	 * @param index
	 * @return
	 */
	private String[] getVMs(int index)
	{
		String[] result=new String[getNumOfVMs(index)];
		int k=0;
		for(int i=0;i< partizionamento.length ; i++)
		{
			if (partizionamento[i]==index)
			{
				result[k]=this.vms[i];
				k++;
			}
		}
		return result;
	}
	
	/* Metodo usato solo per fini di debugging
	private void printMatrix(String description, int[][] matrix)
	{
		System.out.println();
		System.out.println(description);
		for(int i=0; i<matrix.length ; i++)
		{
			for(int j=0; j<matrix.length ; j++)
			{
				System.out.print(" "+matrix[i][j]);
			}
			System.out.println();
		}
		System.out.println();
	}
	*/
}
